python中模块、包、库的概念与从构建到使用及发布的流程。
模块 Module
模块是以单个文件为命名空间的代码片段。
模块是一个单独的.py文件,模块名就是文件名
模块中可以引入其他模块,并由一些python语法来约束和管理
1
2
3graph TD
mk1((模块1 a.py))
mk2((模块2 b.py))模块的构建
构建原则——如何编写一个好的.py文件:
- 功能闭包:一个模块实现单一且完整的功能
- 抽象适度:用函数或类进行抽象,结合功能选择合适抽象
- 操作闭包:模块无顶层可执行语句,导入时无输出。
- 在if-name-main 中编写单元测试部分
- 模块首行编写模块描述。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16# 文件名:a.py
# 这是一个实例模块:包含全局变量,类,函数,可执行语句
var = 3
_var1 = 0.2
__var2 = 3+4j
class MyClass(object):
ClassVar = 'hello'
def func(self):
print('MyClass.func called')
def fun():
print('fun called')
print('print statement')模块的使用
直接运行。
解释器会逐条运行代码,依次在内存中生成三个全局变量对象,生成一个类对象,生成一个函数对象,以及执行print语句
模块外部引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22# b.py
# 演示在b模块中引入a模块
import a
print(a.var)
print(a._var1)
print(a.__var2)
mc = a.MyClass()
print(a.MyClass.ClassVar)
mc.func()
a.fun()
'''
print statement
3
0.2
(3+4j)
hello
MyClass.func called
fun called
'''可以看出import a 后,a中的所有代码都被执行了一遍,生成了各种对象,执行了所有语句。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# b.py
# 演示在b模块中引入a模块
from a import *
print(var)
try:
print(_var1)
except NameError:
print('无法导入变量名以单下划线开头的对象')
try:
print(__var2)
except NameError:
print('无法导入变量名以双下划线开头的对象')1
2
3
4
5
6
7
8
9
10
11
12
13mc = MyClass()
print(MyClass.ClassVar)
mc.func()
fun()
'''
print statement
3
无法导入变量名以单下划线开头的对象
无法导入变量名以双下划线开头的对象
hello
MyClass.func called
fun called
'''from … import …这种给导入方式无法导入单下划线开头和双下划线开头的对象
模块的属性
__name__
模块或包的名字,例如a.__name__
这个属性的值在自身模块中是__main__,在别的模块中是模块名本身
它可以用来区分程序以脚本方式直接执行还是以模块方式被引用执行。
这也就是代码中常见的 if __name__ == ‘__main__’:的原因
包 Package
包是由一组模块构成、有层次命名空间的程序功能
包由多个模块(多个.py文件)有组织的构成
模块的组织方式构成了命名空间的层次结构
包是模块的上一级组织概念,其中可以包括子包
每个包需要包含一个__init__.py文件表达包的组织,这也是与普通包含.py文件目录的区别
__init__.py可以为空,但必须存在。
可以简单理解成:包是目录,模块是.py文件
不同类型的包创建与使用
常规包 Regular Packages:通过__init__.py文件对文件和目录组织形成的包。目录和文件在文件系统上连续。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19graph TD
a1[包 <br>pkg]
b1[包 <br>pkg1]
b2[包 <br>pkg2]
b3((模块 <br>__init__.py))
c1((模块 <br>m1.py))
c2((模块 <br>m2.py))
c3((模块 <br>__init__.py))
c4((模块 <br>m1.py))
c5((模块 <br>__init__.py))
a1---b1
a1---b2
a1---b3
b1---c1
b1---c2
b1---c3
b2---c4
b2---c51
2# pkg/__init__.py
print('导入pkg')1
2# pkg1/__init__.py
print('导入pkg1')1
2# pkg2/__init__.py
print('导入pkg2')1
2
3# pkg.pkg1.m1
def qqq():
print('pkg.pkg1.m1')1
2
3# pkg.pkg1.m2
def qqq():
print('pkg.pkg1.m2')1
2
3# pkg.pkg1.m1
def qqq():
print('pkg.pkg2.m1')1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17# 与pkg同级 c.py
import pkg.pkg1.m1
import pkg.pkg1.m2
import pkg.pkg2.m1
pkg.pkg1.m1.qqq()
pkg.pkg1.m2.qqq()
pkg.pkg2.m1.qqq()
'''
导入pkg
导入pkg1
导入pkg2
pkg.pkg1.m1
pkg.pkg1.m2
pkg.pkg2.m1
'''当包/子包被导入时,对应目录的init文件被执行。
每个包仅被导入一次,且包导入按照层次结构进行。
1
2
3
4
5
6
7
8
9
10
11
12import pkg.pkg1
pkg.pkg1.m1.qqq()
pkg.pkg1.m2.qqq()
'''
导入pkg
导入pkg1
Traceback (most recent call last):
File "D:/PycharmProjects/untitled1/a.py", line 3, in <module>
pkg.pkg1.m1.qqq()
AttributeError: module 'pkg.pkg1' has no attribute 'm1'
'''import 到包是不行的,必须import到模块
1
2
3
4
5
6
7
8
9
10from pkg.pkg1 import m1,m2
m1.qqq()
m2.qqq()
'''
导入pkg
导入pkg1
pkg.pkg1.m1
pkg.pkg1.m2
'''from … import … 直接导入具体模块,可以简化调用时命名空间表达
1
2
3
4
5
6
7
8
9
10
11
12from pkg.pkg1 import *
m1.qqq()
m2.qqq()
'''
导入pkg
导入pkg1
Traceback (most recent call last):
File "D:/PycharmProjects/untitled1/a.py", line 3, in <module>
m1.qqq()
NameError: name 'm1' is not defined
'''如果要使用from … import * 这一功能,还需要在被导入的包的init文件中表述当前包中有哪些模块
1
2# pkg/pkg1/__init__.py
__all__ = ['m1', 'm2']命名空间包 Namespace Packages:由更分散子包组成的包,是一种逻辑包。子包可以是文件系统中分散的包,可以是压缩文件,也可以是网络文件或其他系统资源。
命名空间中各子包并不包含init文件,也就是普通目录。
python解释器通过sys.path变量来隐式维护命名空间包。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21graph TD
p1[project1]
p2[project2]
p1[project1]
pkg1[pkg1]
pkg2[pkg2]
pkg3[pkg1]
pkg4[pkg2]
p1---pkg1
p1---pkg2
p2---pkg3
p2---pkg4
pkg1---m1((m1.py))
pkg2---m((m1.py))
pkg2---m2((m2.py))
pkg3---m3((m3.py))
pkg4---mm3((m3.py))
pkg4---m4((m4.py))1
2
3# project1/pkg1/m1.py
def qqq():
print('project1/pkg1/m1')1
2
3# project1/pkg2/m1.py
def qqq():
print('project1/pkg2/m1')1
2
3# project1/pkg2/m2.py
def qqq():
print('project1/pkg2/m2')1
2
3# project2/pkg1/m3.py
def qqq():
print('project2/pkg1/m3')1
2
3# project2/pkg2/m3.py
def qqq():
print('project2/pkg2/m3')1
2
3# project2/pkg2/m4.py
def qqq():
print('project2/pkg2/m4')1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17# project1,2的同级目录下test.py
import sys
sys.path += ['project1', 'project2'] # 指定import搜索模块或包的路径列表,路径是绝对或相对路径。可以通过append或+来手动添加搜索路径
import pkg1.m1
import pkg1.m3
pkg1.m1.qqq()
pkg1.m3.qqq()
print(pkg1.__path__)
'''
project1/pkg1/m1
project2/pkg1/m3
_NamespacePath(['project1\\pkg1', 'project2\\pkg1'])
'''这样就可以把两个pkg1在逻辑上整合起来。
包的属性
__all__属性
描述包内所有可以用*导入的模块名称,列表类型
__path__属性
记录了某个包(命名空间)的绝对或相对路径,列表类型
常规包的路径是单一的,列表中只有一个元素
命名空间包的路径是多元的,列表中可能有多个元素
库 Library
啥是库
包含一些程序功能,通过import引入使用,对应模块和包。
特定功能代码封装的集合。
如time库,爬虫框架scrapy库,web框架Django库,数据分析pandas库等等。
库是通俗说法,具体指python的模块和包。
python库的核心是模块及模块的组织方式(体现为命名空间)
有哪些库?
标准库(Standard Library)
python解释器自带,无需安装
不超过300个,如os,sys,re等库
功能可靠,整体质量较好,编程常用,总体空间占用不大,由社区贡献并被遴选
第三方库(Third-Party Library)
需要额外安装的python功能库0
超过14万个,可以由任何人编写贡献,如requests,numpy,scrapy等
功能多样,良莠不齐,需要由程序员自我甄别,社区仅维护目录
如何挑选第三方库?
第三方库主站PYPI
pypi:Python Package Index
PSF维护的展示全球python计算生态的主站,仅维护列表
通过搜索及分类等多种方式检索第三方库
结合专家或专业程序员推荐使用
参考python123平台的推荐
判断第三方库可用性的参考指标
- 查看开发历史,近半年有更新记录
- 访问项目主页,文档齐全
- 评星较高
安装第三方库?
pip安装(最常用、最主流)
pip是python解释器自带的命令行第三方库安装工具
遇到GFW 使用国内源 pip install packagename -i –tursted-host http://pypi.douban.com/simple/
需要联网或给定安装文件
UCI安装(pip高级用法)
某些第三方库在pip下载后,需要windows下编译再安装,本地环境没有编译器时,就 需要到网上下载编译好的版本
地址:http://www.lfd.uci.edu/~gohlke/pythonlibs/
在UCI页面上搜索对应的第三方库
下载对应python解释器的编译后版本
本地安装 pip install 下载文件名
自动化pip安装脚本(pip高级用法)
安装20个指定的第三方库
1
2
3
4
5
6
7
8
9
10# 自动联网 自动安装 简单错误处理
import os
libs = ['numpy', 'pillow', 'flask']
try:
for lib in libs:
os.system('pip install %s'%lib)
print('Done')
except:
print('error')集成安装
结合特定python开发工具的批量安装
anaconda(数据计算领域套件,包含800多个第三方库)
使用第三方库
阅读官方文档,查找技术博客、教学视频等等。
理解设计及应用理念
实践典型及拓展案例
构建并发布第三方库
第三方库的发布概念
Pypi:Python Package Index,用来登记第三方库信息
Github bitbucket:用来存储第三方库源代码及文档
项目project:pypi上一组发布和文件的统称
发布release:项目的一个特定版本,每个发布有一个确定的版本号
文件file:即package,一次发布包含的具体文件
发布工具:
- setuptools:将.py源代码打包成一种分发形式
- twine:将打包好的分发形式提交到pypi上
- wheel
更新发布工具:
1
python -m pip install --user --upgrade setuptools wheel twine
第三方库的发布流程
pypi上注册一个账号
整理目录结构
1
2
3
4D:\MYPROJECT
└─mpj
xxx.py
__init__.pyMYPROJECT:最顶层目录,相当于当前项目,所有setuptools命令在这个目录中执行
mpj 0.0.1:包目录,分发的主体,所有源代码及层次化命名空间放这里
创建其他相关文件
1
2
3
4
5
6
7
8D:\MYPROJECT
│ LICENSE
│ README.md
│ setup.py
│
└─mpj
xxx.py
__init__.pysetup.py:配置发布信息的文件
README.md:markdown格式的自述文件
LICENSE:版权声明文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22# setup.py
import setuptools
with open("README.md", "r") as fh:
long_description = fh.read()
setuptools.setup(
name="mpj",
version="0.0.2",
author="xxx",
author_email="xxx@xxx.xxx",
description="xxx",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/xxx",
packages=setuptools.find_packages(),
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
)执行打包命令
在项目目录下执行:
1
python setup.py sdist bdist_wheel
其中sdist参数表示源代码发布,生成.tar.gz文件。对于纯python编写的库,源发布有最佳的适用性。
bdist_wheel表示编译后可执行程序发布,生成whl文件。对于各类多语言的扩展库,节省了本机编译需求,适用性较好。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25D:\MYPROJECT
│ LICENSE
│ README.md
│ setup.py
│
├─build
│ ├─bdist.win-amd64
│ └─lib
│ └─mpj
│ xxx.py
│ __init__.py
│
├─dist
│ mpj-0.0.2-py3-none-any.whl
│ mpj-0.0.2.tar.gz
│
├─mpj
│ xxx.py
│ __init__.py
│
└─mpj.egg-info
dependency_links.txt
PKG-INFO
SOURCES.txt
top_level.txt执行发布命令
1
twine upload dist/*